package com.edan.partnerback.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.edan.partnerback.common.Constants;
import com.edan.partnerback.common.VO.LoginDTO;
import com.edan.partnerback.common.VO.UserRequest;
import com.edan.partnerback.common.enums.emailStatus;
import com.edan.partnerback.entity.Permission;
import com.edan.partnerback.entity.Role;
import com.edan.partnerback.entity.RolePermission;
import com.edan.partnerback.entity.User;
import com.edan.partnerback.exception.ServiceException;
import com.edan.partnerback.mapper.UserMapper;
import com.edan.partnerback.service.IPermissionService;
import com.edan.partnerback.service.IRoleService;
import com.edan.partnerback.service.IUserService;
import com.edan.partnerback.service.RolePermissionService;
import com.edan.partnerback.utils.EmailUtils;
import com.edan.partnerback.utils.RedisCache;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author edan
 * @since 2024-03-28
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    EmailUtils emailUtils;
    @Autowired
    RedisCache redisCache;
    @Resource
    IRoleService roleService;
    @Resource
    RolePermissionService rolePermissionService;
    @Autowired
    IPermissionService permissionService;

    @ApiOperation(value = "登录")
    @Override
    public LoginDTO login(User user) {
        User dbUser = null;
        try {
            dbUser = getOne(new QueryWrapper<User>().eq("username", user.getUsername()));
        } catch (Exception e) {
            throw new RuntimeException("数据库异常", e);
        }
        if (Objects.isNull(dbUser)) {
            throw new ServiceException("未找到用户");
        }
        //非对称密码解密
        String decodePassword = SaSecureUtil.rsaDecryptByPrivate(Constants.PRIVATEKEY, dbUser.getPassword());
        if (!user.getPassword().equals(decodePassword)) {
            throw new ServiceException("用户名或密码错误");
        }
        //通过sa-token加入redis中
        StpUtil.login(dbUser.getUid());
        StpUtil.getSession().set("userInfo", dbUser);
        //将token返回给前端
        String tokenValue = StpUtil.getTokenInfo().getTokenValue();
        //查询用户的菜单树
        String roleFlag = dbUser.getRole();
        List<Permission> permissions = getPermissions(roleFlag);
        List<Permission> auths = permissions.stream().filter(permission -> permission.getType() == 3).collect(Collectors.toList());//按钮
        List<Permission> menus = getTreePermissions(permissions);
        return LoginDTO.builder().user(dbUser).token(tokenValue).menus(menus).auths(auths).build();
    }

    public List<Permission> getPermissions(String roleFlag) {
        Role role = roleService.getOne(new QueryWrapper<Role>().eq("flag", roleFlag));//查询出数据库中的角色
        List<Integer> permissionIds = rolePermissionService.list(new QueryWrapper<RolePermission>().eq("role_id", role.getId()))
                .stream()
                .map(RolePermission::getPermissionId)
                .collect(Collectors.toList());
        return permissionService.listByIds(permissionIds);
    }

    public List<Permission> getTreePermissions(List<Permission> permissionList) {
        //将水平菜单转化为树
        List<Permission> tree = permissionList.stream().filter(permission -> permission.getPid() == null).collect(Collectors.toList());
        for (Permission permission : tree) {
            Integer pid = permission.getId();
            List<Permission> level2 = permissionList.stream().filter(permission1 -> pid.equals(permission1.getPid())).collect(Collectors.toList());//二级页面
            permission.setChildren(level2);
        }
        return tree;
    }

    @Override
    public String passwordUpdate(UserRequest userRequest) {
        if (userRequest.getPassword().equals(userRequest.getNewPassword())) {
            throw new ServiceException("新密码不能与旧密码相同");
        }
        User user = getOne(new QueryWrapper<User>().eq("uid", userRequest.getUid()));
        if (Objects.isNull(user)) {
            throw new ServiceException("用户不存在");
        }
        //使用私钥解密
        String password = SaSecureUtil.rsaDecryptByPrivate(Constants.PRIVATEKEY, user.getPassword());
        if (!password.equals(userRequest.getPassword())) {
            throw new ServiceException("密码错误");
        }
        user.setPassword(SaSecureUtil.rsaEncryptByPublic(Constants.PUBLICKEY, userRequest.getNewPassword()));
        boolean success = updateById(user);
        if (!success) {
            throw new RuntimeException("密码修改失败");
        }
        //下线，清楚缓存
        StpUtil.logout(user.getUid());
        // StpUtil.logoutByLoginId(user.getUsername());
        return userRequest.getNewPassword();
    }

    @Override
    public List<User> listNameByIds(List<Integer> userIds) {
        return baseMapper.listNameByIds(userIds);
    }

    @Override
    public void register(UserRequest user) {
        String redisName = emailStatus.REGISTER + ":" + user.getEmail();//使用前缀:username:email作为注册邮箱验证码
        validateEmail(redisName, user.getEmailCode());
        try {
            //转换
            User saveUser = new User();
            BeanUtils.copyProperties(user, saveUser);
            saveUser(saveUser);
        } catch (Exception e) {
            throw new RuntimeException("数据库异常", e);
        }
    }

    /**
     * 校验邮箱
     * 注：由于注册时存在验证码、重置时存在验证码，为防止验证码冲突，验证码应采用多种状态存储
     *
     * @param redisCodeName 缓存中的存储名称、 emailCoe 验证码
     */

    private void validateEmail(String redisCodeName, String emailCoe) {
        String code = redisCache.getCacheObject(redisCodeName);
        if (StrUtil.isBlank(code)) {
            throw new ServiceException("验证码失效");
        }
        if (!emailCoe.equals(code)) {
            throw new ServiceException("验证码错误");
        }
        //清楚缓存
        redisCache.deleteObject(redisCodeName);
    }

    @Override
    public void sendEmail(String email, String type) {
        emailStatus status = emailStatus.getStatus(type);
        if (status == emailStatus.NOTKNOW) {
            throw new ServiceException("邮箱验证类型不存在，请传入正确的类型");
        }
        String redisEmailName = type + ":" + email;
        Long expire = redisCache.getExpire(redisEmailName);
        if (expire != null && expire > 4 * 60) {
            throw new ServiceException("发送邮箱过于频繁");
        }
        String code = RandomUtil.randomNumbers(6);
        log.info("本次验证的code是：{}", code);
        String html = Constants.getEmailHtml(code);

        User user = getOne(new QueryWrapper<User>().eq("email", email));
        //当类型是注册是，需校验邮箱的唯一性
        if (emailStatus.REGISTER.name().equals(type)) {
            if (!Objects.isNull(user))
                throw new ServiceException("邮箱已注册");
        } else if (emailStatus.RESETPASSWORD.name().equals(type)) { //不采用分步走的方式就需要这个
            if (user == null) {
                throw new ServiceException("不存在用户");
            }
        }

        //异步优化处理
        ThreadUtil.execAsync(() -> {
            emailUtils.sendHtml("【Partner交友网】邮箱验证", html, email);

            redisCache.setCacheObject(redisEmailName, code, Constants.EMAIL_TIME_OUT, TimeUnit.MILLISECONDS);
        });


    }


    @Override
    public String passwordReset(UserRequest userRequest) {
        String redisName = emailStatus.RESETPASSWORD + ":" + userRequest.getEmail();
        validateEmail(redisName, userRequest.getEmailCode());
        User user = getOne(new QueryWrapper<User>().eq("email", userRequest.getEmail()));
        // if (Objects.isNull(user)) { //邮箱校验时已检查
        //     throw new ServiceException("用户不存在");
        // }
        //非对称加密
        // BCrypt.hashpw()同样可采用加盐加密 用check验证
        user.setPassword(SaSecureUtil.rsaEncryptByPublic(Constants.PUBLICKEY, Constants.DEFAULT_PASSWORD));
        boolean success = updateById(user);//修改密码
        if (!success) {
            throw new RuntimeException("重置失败");
        }
        return Constants.DEFAULT_PASSWORD;
    }

    @Override
    public void logout(String uid) {
        //退出登录
        StpUtil.logout(uid);
    }

    public User saveUser(User user) {
        //检验数据库
        User dbUser = getOne(new QueryWrapper<User>().eq("username", user.getUsername()));
        if (dbUser != null) {
            throw new ServiceException("用户已注册");
        }
        if (StrUtil.isBlank(user.getName())) {
            user.setName("系统用户" + RandomUtil.randomString(6));//随机的用户昵称
        }
        if (StrUtil.isBlank(user.getPassword())) {
            user.setPassword("root");
        }
        //密码进行非对称加密
        user.setPassword(SaSecureUtil.rsaEncryptByPublic(Constants.PUBLICKEY, user.getPassword()));
        //设置唯一标识id
        user.setUid(IdUtil.fastSimpleUUID());
        boolean save = save(user);
        if (!save) {
            throw new RuntimeException("注册失败");
        }
        return user;
    }
}
